﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {

    /// <summary>
    /// インデックスを保存するコンテナ クラス
    /// </summary>
    /// <remarks>
    /// インデックスバッファーはインデックスを保存する抽象クラスです。
    /// <img src="IndexBufferDiagram.png"/>
    /// 保存するデータの型に依存しない <see cref="IndexBuffer"/> クラスと､
    /// 型に依存する同名の派生クラス<see cref="IndexBuffer{T}"/> が存在します｡
    /// これらのインデックスは頂点と頂点を結びプリミティブを形成するのに使用されます。
    /// DDDで扱うプリミティブには点 <see cref="PrimitiveType.Point"/>, 線 <see cref="Primitive.Line"/>,
    /// <see cref="PrimitiveType.Triangle"/> の3種類があります。
    /// 4角形およびそれ以上の多角形には対応しません。
    /// またインデックスには必ずリスト形式を用い、ストリップ形式は使用しません。
    /// </remarks>
    /// <seealso cref="Mesh"/>
    /// <seealso cref="IndexBuffer{T}"/>
    public abstract class IndexBuffer : Object {
        /// <summary>
        /// プリミティブ タイプ
        /// </summary>
        /// <remarks>
        /// プリミティブの種類を以下の3種類の中から返します｡
        /// <list type="bullete">
        ///   <item><see cref="DDD.Core.PrimitiveType.Point"/> - 点</item>
        ///   <item><see cref="DDD.Core.PrimitiveType.Line"/> - 線</item>
        ///   <item><see cref="DDD.Core.PrimitiveType.Triangle"/> - 3角形</item>
        /// </list>
        /// </remarks>
        public abstract PrimitiveType PrimitiveType { get; }

        /// <summary>
        /// プリミティブ数
        /// </summary>
        /// <remarks>
        /// このインデックスバッファーをすべて描画した時に描画されるプリミティブの数
        /// </remarks>
        public abstract int PrimitiveCount { get; }

        /// <summary>
        /// 1プリミティブあたりのコンポーネント数
        /// </summary>
        /// <remarks>
        /// プリミティブに応じたコンポーネント数が、
        /// <see cref="PrimitiveType"/>に応じて下記の値が返されます｡
        /// <list type="bullet">
        ///   <item><see cref="PrimitiveType.Point"/> = 1</item>
        ///   <item><see cref="PrimitiveType.Line"/> = 2</item>
        ///   <item><see cref="PrimitiveType.Triangle"/> = 3</item>
        /// </list>
        /// </remarks>
        public abstract int Cpp { get; }

        /// <summary>
        /// インデックスの型
        /// </summary>
        /// <remarks>インデックスを1つ保存するのに使用されるデータ型を返します</remarks>
        /// <seealso cref="IndexType"/>
        public abstract IndexType IndexType { get; }

        /// <summary>
        /// インデックスの総数
        /// </summary>
        /// <remarks>
        /// このインデックス バッファーの持つインデックスの個数です。
        /// </remarks>
        public abstract int IndexCount { get; }
    }

    /// <summary>
    /// インデックスを保存するコンテナ クラス
    /// </summary>
    /// <remarks>
    /// 型に依存しない <see cref="IndexBuffer"/> クラスを継承して､型に依存するインデックスを保存するコンテナ クラスを実装します｡
    /// 使用できるインデックスの型(<typeparamref name="T"/>)は､以下のとおりです｡
    /// <list type="bullet">
    ///   <item><c>byte</c></item>
    ///   <item><c>short</c></item>
    ///   <item><c>ushort</c></item>
    ///   <item><c>int</c></item>
    ///   <item><c>uint</c></item>
    /// </list>
    /// </remarks>
    /// <typeparam name="T">インデックスの型</typeparam>
    /// <seealso cref="IndexBuffer{T}"/>
    public class IndexBuffer<T> : IndexBuffer, IEnumerable<T> where T : struct {

        #region Field
        PrimitiveType primitiveType;
        int primitiveCount;
        int cpp;
        IndexType indexType;
        T[] indices;
        #endregion

        #region Constructor

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 指定のプリミティブ型で、<paramref name="num"/> 個のインデックスを保存できるインデックス バッファー オブジェクトを作成します。
        /// インデックスは全て0で初期化されます｡
        /// 使用できるインデックス型 <typeparamref name="T"/> は、以下のとおりです。
        /// <list type="bullet">
        ///   <item><c>byte</c></item>
        ///   <item><c>short</c></item>
        ///   <item><c>ushort</c></item>
        ///   <item><c>int</c></item>
        ///   <item><c>uint</c></item>
        /// </list>
        /// </remarks>
        /// <param name="type">プリミティブの種類</param>
        /// <param name="num">プリミティブ数</param>
        /// <exception cref="ArgumentException"><paramref name="num"/> &lt; 0</exception>
        /// <exception cref="ArgumentException"><typeparamref name="T"/> が <c>byte, sbyte, short, ushort, int, uint</c> 以外の時</exception>
        /// <seealso cref="PrimitiveType"/>
        public IndexBuffer (PrimitiveType type, int num) {
            if (num <= 0) {
                throw new ArgumentException ("Number of primitives is invalid");
            }
            if (!(typeof (T) == typeof (byte) || typeof(T) == typeof(sbyte) ||
                typeof (T) == typeof (short) || typeof (T) == typeof (ushort) ||
                typeof (T) == typeof (int) || typeof (T) == typeof (uint))) {
                throw new ArgumentException ("Index type is invalid");
            }
            this.primitiveType = type;
            this.primitiveCount = num;
            this.cpp = type.Cpp ();
            switch (typeof (T).Name) {
                case "Byte": this.indexType = IndexType.Byte; break;
                case "SByte": this.indexType = IndexType.SByte; break;
                case "Int16": this.indexType = IndexType.Short; break;
                case "UInt16": this.indexType = IndexType.UShort; break;
                case "Int32": this.indexType = IndexType.Int; break;
                case "UInt32": this.indexType = IndexType.UInt; break;
                default: throw new NotImplementedException("Sorry");
            }
            this.indices = new T[num * cpp];
        }
        #endregion

        #region Property

        /// <inheritdoc/>
        public override PrimitiveType PrimitiveType {
            get { return primitiveType; }
        }

        /// <inheritdoc/>
        public override int PrimitiveCount {
            get { return primitiveCount; }
        }

        /// <inheritdoc/>
        public override int Cpp {
            get { return cpp; }
        }

        /// <inheritdoc/>
        public override IndexType IndexType {
            get { return indexType; } 
        }

        /// <inheritdoc/>
        public override int IndexCount {
            get { return indices.Length; }
        }

        /// <summary>
        /// すべてのインデックスを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 保存しているすべてのインデックスを列挙します。
        /// </remarks>
        public IEnumerable<T> Indices {
            get { return indices; }
        }
  
        #endregion

        #region Method
        /// <inheritdoc/>
        IEnumerator<T> IEnumerable<T>.GetEnumerator () {
            return ((IEnumerable<T>)indices).GetEnumerator ();
        }

        /// <inheritdoc/>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return indices.GetEnumerator ();
        }


        /// <summary>
        /// インデックスを取得します
        /// </summary>
        /// <remarks>
        /// 指定のインデックス <paramref name="firstPrimitive"/> から <paramref name="numPrimitives"/> 個のプリミティブに相当するインデックスを取得します。
        /// すべてプリミティブ換算で指定する事に注意してください。
        /// <note type="implement">
        /// <paramref name="dst"/> = <c>null</c> の時に何もしない実装になっているが、
        /// 通常 <see cref="ArgumentNullException"/> を発生していたはず。
        /// 修正するかどうか後で考える。
        /// </note>
        /// </remarks>
        /// <param name="firstPrimitive">取得を始める最初のプリミティブのインデックス</param>
        /// <param name="numPrimitives">取得するプリミティブの数</param>
        /// <param name="dst">結果を書き込む <typeparamref name="T"/> 型の配列</param>
        /// <exception cref="ArgumentException"><paramref name="firstPrimitive"/> &lt; 0 または <paramref name="firstPrimitvie"/> &gt;= <paramref name="PrimitiveCount"/></exception>
        /// <exception cref="ArgumentException"><paramref name="numPrimitive"/> &lt; 0 または <paramref name="firstPrimitive"/> + <paramref name="numPrimitives"/> &gt; <see cref="PrimitiveCount"/></exception>
        /// <exception cref="ArgumentException"><paramref name="dst"/>.<see cref="VertexArray.Length"/> &lt; <see cref="numPrimitives"/> * <see cref="Cpp"/></exception>
        public void Get (int firstPrimitive, int numPrimitives, T[] dst) {
            if (firstPrimitive < 0 || firstPrimitive >= PrimitiveCount) {
                throw new ArgumentException ("First primitive is invalid");
            }
            if (numPrimitives < 0 || firstPrimitive + numPrimitives > PrimitiveCount) {
                throw new ArgumentException ("Number of primitives is inalid");
            }
            if (dst == null) {
                return;
            }
            if (dst.Length < numPrimitives * Cpp) {
                throw new ArgumentException ("Dst is too short");
            }

            Array.Copy (this.indices, firstPrimitive * Cpp, dst, 0, numPrimitives * Cpp);
        }

        /// <summary>
        /// インデックスの書き込み
        /// </summary>
        /// <remarks>
        /// インデックスを書き込みます。インデックスはすべて内部に確保したバッファーにコピーされます。
        /// </remarks>
        /// <param name="firstPrimitive">書き込みを始める最初のプリミティブ インデックス</param>
        /// <param name="numPrimitives">書き込みするプリミティブの数</param>
        /// <param name="src">データを読み込む <typeparamref name="T"/> 型の配列</param>
        /// <exception cref="ArgumentException"><paramref name="firstPrimitive"/> &lt; 0 または <paramref name="firstPrimitvie"/> &gt;= <paramref name="PrimitiveCount"/></exception>
        /// <exception cref="ArgumentException"><paramref name="numPrimitive"/> &lt; 0 または <paramref name="firstPrimitive"/> + <paramref name="numPrimitives"/> &gt; <see cref="PrimitiveCount"/></exception>
        /// <exception cref="ArgumentNullException"><paramref name="src"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="src"/>.<see cref="VertexArray.Length"/> &lt; <see cref="numPrimitives"/> * <see cref="Cpp"/></exception>
        public void Set (int firstPrimitive, int numPrimitives, T[] src) {
            if (firstPrimitive < 0 || firstPrimitive >= PrimitiveCount) {
                throw new ArgumentException ("First primitive is invalid");
            }
            if (numPrimitives < 0 || firstPrimitive + numPrimitives > PrimitiveCount) {
                throw new ArgumentException ("Number of primitives is inalid");
            }
            if (src == null) {
                throw new ArgumentNullException ("Src is null");
            }
            if (src.Length < numPrimitives * Cpp) {
                throw new ArgumentException ("Src is too short");
            }

            Array.Copy (src, 0, this.indices, firstPrimitive * Cpp, numPrimitives * Cpp);
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("{0}:{1}", PrimitiveType, PrimitiveCount);
        }
        #endregion


    }
}
